with Ada.Text_IO;             use Ada.Text_IO;
with Ada.Exceptions;          use Ada.Exceptions;
with System.Storage_Elements; use System.Storage_Elements;
with Ada.Integer_Text_IO;     use Ada.Integer_Text_IO;
with Ada.Command_Line;        use Ada.Command_Line;

with Win32API_Interface;                use Win32API_Interface;
with Win32API_Interface.Process;        use Win32API_Interface.Process;
with Win32API_Interface.Process.Helper; use Win32API_Interface.Process.Helper;
with Win32API_Interface.Memory.Helper;  use Win32API_Interface.Memory.Helper;

with Ada.Unchecked_Conversion;

procedure Find is
  Verbose        : Boolean;
  Access_Handle  : Handle;
  Opened         : Boolean;
  Process        : Process_ID;
  FFXIMain_Info  : Module_Info;
  FFXIMain_End   : Address;
  c              : Character;
  Pattern : constant string :=
    "c8,00,00,ff5020ff,00000000,00000000,0022,00,01,00,00,"&
    character'val(16#1E#)&character'val(16#1#)&
    character'val(16#1E#)&character'val(16#1#)&"<<< Welcome to ";

  procedure Put_Info(Name : in string; Addr : in Address) is
  begin
    Put(Name&": ");
    Put(Integer(To_Integer(Addr)), base => 16);
    new_line;
  end Put_Info;

  procedure Put_Info(Name : in string; n : in Integer) is
  begin
    Put(Name&": ");
    Put(n);
    new_line;
  end Put_Info;

  procedure Check_Coherency(Log_Addr, Addr : in Address) is
    RootAddress : Address;
    FirstDereference : Address;
    OffsetFromFirstDereference : Address;
    SecondDereference : Address := System.Null_Address;
    Current_Log_Ptr : Address;
    Dereference_Current_Log_Ptr : Address;

    procedure Fail is
    begin
      if Verbose then
        Put_Line(">>>> Coherency Check failed");
      end if;
    end Fail;
  begin
    RootAddress := Addr;
    FirstDereference := Follow_Pointer(Access_Handle, RootAddress);
    OffsetFromFirstDereference := FirstDereference + 4;
    SecondDereference := Follow_Pointer(Access_Handle,
                                        OffsetFromFirstDereference);
    Current_Log_Ptr := SecondDereference + 204;
    Dereference_Current_Log_Ptr := Follow_Pointer(Access_Handle,
                                                  Current_Log_Ptr);
    if To_Integer(Dereference_Current_Log_Ptr) = To_Integer(Log_Addr) then
      Put_Info(">>>> POSSIBLE FFXiMainStaticOffset",
               To_Address(To_Integer(Addr) -
            To_Integer(FFXIMain_Info.Base_Address)));
    else
      Fail;
    end if;
  exception
    when others => Fail;
  end Check_Coherency;

  procedure Find_FFXiMainStaticOffset(Log_Addr, Addr : in Address) is
    FFXiMainStaticOffset_Results : Results;
  begin
    FFXiMainStaticOffset_Results := Find_Pointer(Access_Handle, Addr,
                                                 FFXIMain_Info.Base_Address,
                                                 FFXIMain_End);
    if Verbose then
      Put_Info(">>> FFXiMainStaticOffset results",
               FFXiMainStaticOffset_Results.n);
    end if;
    for i in 1..FFXiMainStaticOffset_Results.n loop
      Check_Coherency(Log_Addr, FFXiMainStaticOffset_Results.Pos(i));
    end loop;
  end Find_FFXiMainStaticOffset;

  procedure Find_OffsetFromFirstDereference(Log_Addr, Addr : in Address) is
    OffsetFromFirstDereference_Results : Results;
    Possible_FirstDereference : Address;
  begin
    OffsetFromFirstDereference_Results := Find_Pointer(Access_Handle, Addr);
    if Verbose then
      Put_Info(">> OffsetFromFirstDereference Results",
               OffsetFromFirstDereference_Results.n);
    end if;
    for i in 1..OffsetFromFirstDereference_Results.n loop
      Possible_FirstDereference
        := OffsetFromFirstDereference_Results.Pos(i) - 4;
      if Verbose then
        Put_Info(">> Possible_FirstDereference", Possible_FirstDereference);
      end if;
      Find_FFXiMainStaticOffset(Log_Addr, Possible_FirstDereference);
    end loop;
  end Find_OffsetFromFirstDereference;

  procedure Find_Current_Log_Ptr(Addr : in Address) is
    Current_Log_Ptr_Results : Results;
    Possible_SecondDereference : Address;
  begin
    Current_Log_Ptr_Results := Find_Pointer(Access_Handle, Addr);
    if Verbose then
      Put_Info("> CurrentLogPtr Results", Current_Log_Ptr_Results.n);
    end if;
    for i in 1..Current_Log_Ptr_Results.n loop
      Possible_SecondDereference := Current_Log_Ptr_Results.Pos(i) - 204;
      if Verbose then
        Put_Info("> Possible_SecondDereference", Possible_SecondDereference);
      end if;
      Find_OffsetFromFirstDereference(Addr, Possible_SecondDereference);
    end loop;
  end Find_Current_Log_Ptr;

  procedure Follow_Paths is
    First_Search_Results : Results;
  begin
    First_Search_Results := Find_Bytes(Access_Handle, Pattern);
    if Verbose then
      Put_Info("Pattern Results", First_Search_Results.n);
    end if;
    for i in 1..First_Search_Results.n loop
      Find_Current_Log_Ptr(First_Search_Results.Pos(i));
    end loop;
  end Follow_Paths;

  procedure Usage is
  begin
    if Verbose then
      Put_Line("--------------------------------");
      Put_Line("- Find for FFXI Alarm by Eddie -");
      Put_Line("--------------------------------");
      New_Line;
      Put_Line("This program will try to determine the possible FFXiMainStaticOffset constant");
      Put_Line("used by different programs (DirectParse, FFXIAlarm) to achieve reading of Final");
      Put_Line("Fantasy XI's log.");
      New_Line;
      Put_Line("The used method is quite naive, guiven that it's based on finding a pattern");
      Put_Line("given when the 'Welcome to Whateverserver' message appears, so you must use");
      Put_Line("this program right after logging in with a character.");
      New_Line;
      Put_Line("You're expected to be running a single instance of Final Fantasy XI when");
      Put_Line("running this program.");
      New_Line;
      Put_Line("Using this program on XP is recommended, given that on Vista it can provide a");
      Put_Line("number of valid results that aren't compatible with XP, but not the other way");
      Put_Line("around.");
      New_Line;
      Put_Line("It MUST be run under the same privileges than Final Fantasy XI, which will be");
      Put_Line("as Admin in most cases.");
      New_Line;
    end if;
    Put_Line("The process can take up to a few minutes to complete in slow computers.");
    if Verbose then
      Put_Line("Press any key to start the process...");
      Get_Immediate(c);
    else
      Put_Line("Execute the program with any argument for a verbose mode.");
    end if;
    New_Line;
  end Usage;
begin
  Opened := False;
  Verbose := Argument_Count > 0;
  Usage;
  Process := Find("pol.exe");
  Access_Handle := Open(Process);
  Opened := True;
  FFXIMain_Info := Get_Module_Info("FFXiMain.dll", Process);
  FFXIMain_End :=
    FFXIMain_Info.Base_Address + Storage_Offset(FFXIMain_Info.Base_Size);
  Follow_Paths;
  Close(Access_Handle);
  Opened := False;
  New_Line;
  Put_Line("Finished, press any key to exit...");
  Get_Immediate(c);
exception
  when E : others =>
    if Opened then Close(Access_Handle); end if;
    Put_Line(Ada.Exceptions.Exception_Name (E)&" : "&
             Ada.Exceptions.Exception_Message (E));
    Get_Immediate(c);
end Find;
